#pragma once
#include "Helper.hpp"
#include "Location.hpp"
#include <string>
#include <unordered_map>
#include <unordered_set>

namespace Welp
{
  using namespace std;

  namespace TokenType
  {
    enum Enum
    {
      Invalid,

      // Skipped
      Eof,
      Whitespace,
      Comment,

      // Varying
      Number,
      String,
      Identifier,
      Symbol,

      // Keywords
      True,
      False,
      If,
      While,
      Function,

      // Symbols
      StatementSeparator,
      ArgumentSeparator,
      BeginGroup,
      EndGroup,
      BeginScope,
      EndScope,
      BeginIndex,
      EndIndex,
      MemberAccess,
      Plus,
      Minus,
      LogicalNot,
      BitwiseNot,
      Increment,
      Decrement,
      Multiply,
      Divide,
      Modulo,
      Exponent,
      BitshiftLeft,
      BitshiftRight,
      LessThan,
      LessThanOrEqualTo,
      GreaterThan,
      GreaterThanOrEqualTo,
      EqualTo,
      NotEqualTo,
      BitwiseAnd,
      BitwiseXor,
      BitwiseOr,
      LogicalAnd,
      LogicalOr,
      Assignment,
      PlusAssignment,
      MinusAssignment,
      MultiplyAssignment,
      DivideAssignment,
      ModuloAssignment,
      ExponentAssignment,
      BitshiftLeftAssignment,
      BitshiftRightAssignment,
      BitwiseAndAssignment,
      BitwiseXorAssignment,
      BitwiseOrAssignment
    };
  }

  class Token
  {
  public:
    Location mLocation;
    TokenType::Enum mType;
    string mText;
  };


  class DfaState;

  class DfaEdge
  {
  public:
    DfaEdge();

    void AddRange(unsigned char start, unsigned char end);
    unordered_set<char> mValidChars;

    DfaState* mToState;
  };

  class DfaState
  {
  public:
    DfaState();

    DfaEdge* FindEdge(char c);
    
    const char* mName;
    const char* mErrorOnFail;
    vector<DfaEdge*> mOutgoing;

    // If set to invalid, it means we don't accept the token
    // Note: Accepting states can still have outgoing edges
    TokenType::Enum mAcceptingToken;
  };


  class Dfa
  {
  public:

    Dfa();
    ~Dfa();

    vector<DfaEdge*> mAllEdges;
    vector<DfaState*> mAllStates;

    DfaState* mRoot;

    DfaState* CreateState();
    DfaEdge* CreateOutgoingEdge(DfaState* from, DfaState* to);
  };

  class DfaTokenizer
  {
  public:
    
    string mText;
    Location mLocation;

    Dfa mDfa;

    DfaTokenizer();
    void RegisterKeyword(const char* keyword);
    void RegisterSymbol(const char* symbol);

    //void Visualize(DfaEdge* rootEdge);
    
    static void DefaultErrorCallback(const string& message, const Location& location, void* userData);
    void ReadToken(Token* tokenOut);
  };

  
  class Tokenizer
  {
  public:

    unordered_map<string, TokenType::Enum> mStringToFixedToken;
    unordered_map<TokenType::Enum, string> mTokenToString;
    string mText;
    size_t mPosition;

    Tokenizer();

    void ReadAndClassifyToken(Token* tokenOut);
    void ReadToken(Token* tokenOut);
  };
}